package com.b2j.base;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.b2j.language.And;
import com.b2j.language.Case;
import com.b2j.language.Default;
import com.b2j.language.Else;
import com.b2j.language.ElseIf;
import com.b2j.language.For;
import com.b2j.language.Frame;
import com.b2j.language.From;
import com.b2j.language.Function;
import com.b2j.language.If;
import com.b2j.language.Import;
import com.b2j.language.Loop;
import com.b2j.language.Or;
import com.b2j.language.Program;
import com.b2j.language.Process;
import com.b2j.language.Repeat;
import com.b2j.language.Return;
import com.b2j.language.SubExpresion;
import com.b2j.language.Switch;
import com.b2j.language.Type;
import com.b2j.language.Until;
import com.b2j.language.While;
import com.b2j.routines.Advance;
import com.b2j.routines.Collision;
import com.b2j.routines.Exit;
import com.b2j.routines.Key;
import com.b2j.routines.LoadFnt;
import com.b2j.routines.LoadFpg;
import com.b2j.routines.LoadPng;
import com.b2j.routines.S_KILL;
import com.b2j.routines.SetFps;
import com.b2j.routines.SetMode;
import com.b2j.routines.Signal;
import com.b2j.routines.audio.LoadSong;
import com.b2j.routines.audio.LoadWav;
import com.b2j.routines.audio.PauseSong;
import com.b2j.routines.audio.PlaySong;
import com.b2j.routines.audio.PlayWav;
import com.b2j.routines.audio.ResumeSong;
import com.b2j.routines.grproc.GetAngle;
import com.b2j.routines.grproc.GetDist;
import com.b2j.routines.map.G_HEIGHT;
import com.b2j.routines.map.G_WIDTH;
import com.b2j.routines.map.MapInfo;
import com.b2j.routines.math.FGetAngle;
import com.b2j.routines.math.FGetDist;
import com.b2j.routines.text.DeleteText;
import com.b2j.routines.text.Write;
import com.b2j.variables.Mouse;

public class Tools {

	public String programName = "";
	public String screenWidth = "";
	public String screenHeight = "";
	public List<Lexema> listLexema;
	public int currentLexema = 0;
	public Lexema lexema;
	public Map<String,ProcessData> process;
	public ProcessData currentProcess;
	
	public Map<Variable.Type, String> types;
	public Map<String,String> reservedVariableNames; 
	
	public Tools() {
		
		this.listLexema = new ArrayList<Lexema>();
		this.process = new TreeMap<String, ProcessData>();
		this.types = new TreeMap<Variable.Type, String>();
		this.reservedVariableNames = new TreeMap<String, String>();
		this.loadInitialsProcess();
		
	}
	
	public void parseTo(Token token) {
		
		lexema = this.listLexema.get(currentLexema);
		while (lexema.tokenType != token && currentLexema < this.listLexema.size()) {
			lexema = this.nextLexema();
		}
		
	}
	
	public void lex() {
		if (currentLexema < this.listLexema.size()) {
			lexema = this.listLexema.get(currentLexema);
		}
		currentLexema++;
	}
	
	public Lexema nextLexema() {
		return this.listLexema.get(++currentLexema);
	}
	
	public void resetLex() {
		currentLexema = 0;
	}
	
	public void loadTokenList(String path) {
		
		BufferedReader br;
		try {
			br = new BufferedReader(new FileReader(path));
			Lexer lexer = new Lexer(br);
			
			Lexema l = lexer.yylex(); 
			while (l != null) {
				if (l.tokenType == Token.ID)
					l.lexema = l.lexema.toLowerCase();
				this.listLexema.add(l);
				l = lexer.yylex();
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	// Fill the variableList
	public void buildVariablesList() {
	
		boolean variablesArea = false;
		int openAreas = 0; 		// Const, Global, Local and private add 1 to this variables. It is to control the open variables Areas.
								// The END token subtract 1 from this and when it is 0 the variablesArea is set to false.
		
		Variable.Scope typeScope = Variable.Scope.GLOBAL;
		String nameScope = "";
		String prevNameScope = "";
		boolean isConstant = false;
		
		ProcessData.Type processType = ProcessData.Type.PROCESS;
		String processReturnType = "";
		
//		ProcessData currentProcess = null;
		
		for ( currentLexema = 0; currentLexema < this.listLexema.size(); currentLexema++) {
			
			Lexema l = this.listLexema.get(currentLexema);
			if (l != null) {
				
				// Take the scope
				if (l.tokenType == Token.CONST) {
					
					nameScope = "const";
					typeScope = Variable.Scope.GLOBAL;
					variablesArea = true;
					openAreas++;
					isConstant = true;
					processType = ProcessData.Type.SCOPE;
					
				} else if (l.tokenType == Token.GLOBAL) {
					
					nameScope = "global";
					typeScope = Variable.Scope.GLOBAL;
					variablesArea = true;
					openAreas++;
					isConstant = false;
					processType = ProcessData.Type.SCOPE;
					
				} else if (l.tokenType == Token.LOCAL) {
					
					nameScope = "local";
					typeScope = Variable.Scope.LOCAL;
					variablesArea = true;
					openAreas++;
					isConstant = false;
					processType = ProcessData.Type.SCOPE;
					
				} else if (l.tokenType == Token.PRIVATE) {
					
					typeScope = Variable.Scope.PRIVATE;
					variablesArea = true;
					openAreas++;
					isConstant = false;
					
				} else if (l.tokenType == Token.END) {
					
					if (openAreas > 0)
						openAreas--;
					
					if (openAreas <= 0) 
						variablesArea=false;
					isConstant = false;

				}else if (l.tokenType == Token.PROCESS || l.tokenType == Token.FUNCTION) {
					
					variablesArea = false;
					isConstant = false;
					
					// Set the process type when is a process or function
					if (l.tokenType == Token.PROCESS)
						processType = ProcessData.Type.PROCESS;
					else if (l.tokenType == Token.FUNCTION)
						processType = ProcessData.Type.FUNCTION;
					
					l = this.listLexema.get(++currentLexema);
					
					// If the token next to PROCESS or FUNCTION is a language component, then it is a return type
					if (l.group == TokenGroup.LANGUAGE_COMPONENT) {
					
//						processReturnType = l.lexema;
						Variable.Type returnType = Variable.Type.INT; 
						if (l.tokenType == Token.FLOAT)
							returnType = Variable.Type.FLOAT;
						else if (l.tokenType == Token.STRING)
							returnType = Variable.Type.STRING;
						
						processReturnType = this.getTypeCode(returnType);
						l = this.listLexema.get(++currentLexema);
						
					}
					
					if (l.tokenType == Token.ID) {
						nameScope = l.lexema;
					} else {
						System.out.println("ERROR de Sintaxis");
						return;
					}
					
				}
				
				// If the scope change, create a new process data
				if (!nameScope.equals(prevNameScope)) {
					
					prevNameScope = nameScope;
//					currentProcess = this.process.get(nameScope);
					this.getProcess(nameScope);
					if (currentProcess == null) {
						currentProcess = new ProcessData(processType, nameScope, processReturnType);
						this.process.put(nameScope, currentProcess);
					}
					
				} else {

					if ((l.tokenType == Token.INT || l.tokenType == Token.FLOAT || l.tokenType == Token.STRING || l.tokenType == Token.ID) && variablesArea ) {
					
						processVariable(l, currentProcess, typeScope, isConstant);
						
						
					}
					
					
				}
				
			}
			
		}
		
		
	}

	public void processVariable(Lexema l, ProcessData currentProcess, Variable.Scope typeScope, boolean isConstant) {

		if (typeScope == null)
			typeScope = Variable.Scope.PARAMETER;
		
		// Default type
		Variable.Type type = Variable.Type.INT;
		
		// Set the variable type
		if (l.tokenType == Token.FLOAT) 
			type = Variable.Type.FLOAT;
		else if (l.tokenType == Token.STRING)
			type = Variable.Type.STRING;
		
		String name = "";
		String initialization = "";
		int elements = 0;

		if (l.tokenType != Token.ID)
			l = this.listLexema.get(++currentLexema);

		if (l.tokenType == Token.ID) {
			
			name = l.lexema;
			
			if (typeScope != Variable.Scope.PARAMETER) {
				
				l = this.listLexema.get(++currentLexema);
				
				if (l.tokenType == Token.ARRAY) {
					String num = l.lexema.substring(1, l.lexema.length() - 1);
					elements = new Integer(num);
					l = this.listLexema.get(++currentLexema);
				}
				while (l.tokenType != Token.SEMICOLON) {
					initialization = initialization + l.lexema;
					l = this.listLexema.get(++currentLexema);								
				}
				
				// If the initialization contains a '"' then the variable type is string
				if (!initialization.isEmpty()) {
					if (initialization.contains("\""))
						type = Variable.Type.STRING;
					else if (this.isInitializationFloat(initialization))
						type = Variable.Type.FLOAT;
				}
				
			}
			
			// Verify if the variable declaration have a // PID comment, it indicate that the variable is a id of Process
			l = this.listLexema.get(++currentLexema);
			if (l.tokenType == Token.COMMENTS) {
				String comment = new String(l.lexema);
				comment.toUpperCase();
				if (comment.contains("PID"))
					type = Variable.Type.PROCESSID;
			} else {
				
				currentLexema--;
				
			}
			
			Variable var = new Variable(typeScope, type, name, initialization);
			var.isConstant = isConstant;
			var.elements = elements;
			currentProcess.listVariables.add(var);
			
			if (var.type == Variable.Type.PROCESSID) {
				if (currentProcess.imports.indexOf("import com.jdiv.ProcessJDiv;") < 0)
					currentProcess.imports.add("import com.jdiv.ProcessJDiv;");
			}
				
		
		} else {
			System.out.println("ERROR de sintaxis");
			return;
		}

	}

	public String formatClassName(String className) {
		return className.substring(0,1).toUpperCase() + className.substring(1, className.length()).toLowerCase();
	}
	
	// Verify if the initialization expression corresponding to a float value
	public boolean isInitializationFloat(String line) {
	
		boolean ret = false;
		
		String pattern = "([\\+|\\-]*[0-9]+(\\.[0-9]+)*)";
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(line);
		
		while (m.find() && !ret) {
			
			if (m.group(1) != null && m.group(2) != null)
				ret = true;

		}
		
		return ret;
	}
	
	// Complete the process parameters list
	public void buildProcessList() {
		
		for (currentLexema = 0; currentLexema < this.listLexema.size(); currentLexema++) {
			Lexema l = this.listLexema.get(currentLexema);
			if (l.tokenType == Token.PROCESS || l.tokenType == Token.FUNCTION) {
				
				l = this.listLexema.get(++currentLexema);
				
				// If next to PROCESS or FUNCTION is a language component, then it is a return type and discard it
				if (l.group == TokenGroup.LANGUAGE_COMPONENT)
					l = this.listLexema.get(++currentLexema);
				
//				ProcessData currentProcess = this.process.get(l.lexema);
				this.getProcess(l.lexema);
				if (currentProcess != null) {
					
					l = this.listLexema.get(++currentLexema);
					while (l.tokenType != Token.CPARENT) {
						
						if (l.tokenType == Token.INT || l.tokenType == Token.FLOAT || l.tokenType == Token.STRING || l.tokenType == Token.ID) {
							
							processVariable(l, currentProcess, null, false);
							
						}
						
						l = this.listLexema.get(++currentLexema);
						
					}
				
				}
				
			}
			
		}
		
	}
	
	// Process identifiers
	public String identifier(Lexema lexema) {
		
		String name = lexema.lexema;
		String line = "";
			
		name = name.toLowerCase();
		ProcessData p = this.process.get(name);
		
		if ( p != null) {
			
			// When is a process or function
			line = p.generator.generate(lexema);
			
		} else {
			
			boolean isGlobal = false;
			p = this.process.get("global");
			if (p != null) {

				// If the process type is Scope
				if (p.type == ProcessData.Type.SCOPE) {
					
					// When is a Global variable
					for (int i = 0; i < p.listVariables.size() && !isGlobal; i++)
						if (p.listVariables.get(i).name.equals(name)) {
							isGlobal = true;
						}
				
				}
				
			} 
				
			if (!isGlobal) {
				p = this.process.get("const");
				if (p != null) {
					
					// When is a Constant
					for (int i = 0; i < p.listVariables.size() && !isGlobal; i++)
						if (p.listVariables.get(i).name.equals(name)) {
							isGlobal = true;
						}
				}
					
			}
			
			if (isGlobal) 
				line = line + "GlobalVars." + name;
			else
				line = name;
			
		}
		
		return line;
		
	}
	
	
	public void loadInitialsProcess() {
		
		// Fill the types table
		
		this.types.put(Variable.Type.INT, "int");
		this.types.put(Variable.Type.FLOAT, "float");
		this.types.put(Variable.Type.STRING, "String");
		this.types.put(Variable.Type.PROCESSID, "ProcessJDiv");
		
		// Fill the reserved variable names table
		
		this.reservedVariableNames.put("x", "");
		this.reservedVariableNames.put("y", "");
		this.reservedVariableNames.put("graph", "");
		this.reservedVariableNames.put("file", "");
		this.reservedVariableNames.put("angle", "");

		// - LANGUAGE
		
		// PROGRAM
		
		ProcessData program = new ProcessData(ProcessData.Type.LANGUAGE, "program");
		program.generator = new Program();
		this.process.put(program.name, program);

		// IMPORT
		
		ProcessData iImport = new ProcessData(ProcessData.Type.LANGUAGE, "import");
		iImport.generator = new Import();
		this.process.put(iImport.name, iImport);
		
		// PROCESS
		
		ProcessData process = new ProcessData(ProcessData.Type.LANGUAGE, "process");
		process.generator = new Process();
		this.process.put(process.name, process);

		// PROCESS
		
		ProcessData function = new ProcessData(ProcessData.Type.LANGUAGE, "function");
		function.generator = new Function();
		this.process.put(function.name, function);
		
		// FOR
		
		ProcessData iFor = new ProcessData(ProcessData.Type.LANGUAGE, "for");
		iFor.generator = new For();
		this.process.put(iFor.name, iFor);
		
		// WHILE

		ProcessData iWhile = new ProcessData(ProcessData.Type.LANGUAGE, "while");
		iWhile.generator = new While();
		this.process.put(iWhile.name, iWhile);

		// LOOP

		ProcessData loop = new ProcessData(ProcessData.Type.LANGUAGE, "loop");
		loop.generator = new Loop();
		this.process.put(loop.name, loop);

		// REPEAT

		ProcessData repeat = new ProcessData(ProcessData.Type.LANGUAGE, "repeat");
		repeat.generator = new Repeat();
		this.process.put(repeat.name, repeat);

		// UNTIL

		ProcessData until = new ProcessData(ProcessData.Type.LANGUAGE, "until");
		until.generator = new Until();
		this.process.put(until.name, until);

		// IF

		ProcessData iIf = new ProcessData(ProcessData.Type.LANGUAGE, "if");
		iIf.generator = new If();
		this.process.put(iIf.name, iIf);
		
		// ELSE

		ProcessData iElse = new ProcessData(ProcessData.Type.LANGUAGE, "else");
		iElse.generator = new Else();
		this.process.put(iElse.name, iElse);

		// ELSEIF

		ProcessData elseif = new ProcessData(ProcessData.Type.LANGUAGE, "elseif");
		elseif.generator = new ElseIf();
		this.process.put(elseif.name, elseif);

		// SWITCH

		ProcessData iSwitch = new ProcessData(ProcessData.Type.LANGUAGE, "switch");
		iSwitch.generator = new Switch();
		this.process.put(iSwitch.name, iSwitch);

		// CASE

		ProcessData iCase = new ProcessData(ProcessData.Type.LANGUAGE, "case");
		iCase.generator = new Case();
		this.process.put(iCase.name, iCase);

		// DEFAULT

		ProcessData iDefault = new ProcessData(ProcessData.Type.LANGUAGE, "default");
		iDefault.generator = new Default();
		this.process.put(iDefault.name, iDefault);

		// AND

		ProcessData and = new ProcessData(ProcessData.Type.LANGUAGE, "and");
		and.generator = new And();
		this.process.put(and.name, and);

		// OR

		ProcessData or = new ProcessData(ProcessData.Type.LANGUAGE, "or");
		or.generator = new Or();
		this.process.put(or.name, or);

		// FROM

		ProcessData from = new ProcessData(ProcessData.Type.LANGUAGE, "from");
		from.generator = new From();
		this.process.put(from.name, from);
		
		// ( - Sub-expression '(expression)'
		
		ProcessData subExpresion = new ProcessData(ProcessData.Type.LANGUAGE, "(");
		subExpresion.generator = new SubExpresion();
		this.process.put(subExpresion.name, subExpresion);
		
		// FRAME
		
		ProcessData frame = new ProcessData(ProcessData.Type.LANGUAGE, "frame");
		frame.generator = new Frame();
		this.process.put(frame.name, frame);

		// RETURN
		
		ProcessData iReturn = new ProcessData(ProcessData.Type.LANGUAGE, "return");
		iReturn.generator = new Return();
		this.process.put(iReturn.name, iReturn);

		// ROUTINES
		
		// KEY
		
		ProcessData key = new ProcessData(ProcessData.Type.LANGUAGE, "key");
		key.generator = new Key();
		this.process.put(key.name, key);
		
		// TYPE
		
		ProcessData type = new ProcessData(ProcessData.Type.LANGUAGE, "type");
		type.generator = new Type();
		this.process.put(type.name, type);
				
		// LOAD_PNG
		
		ProcessData loadPng = new ProcessData(ProcessData.Type.LANGUAGE, "load_png");
		loadPng.generator = new LoadPng();
		this.process.put(loadPng.name, loadPng);
		
		// LOAD_FPG
		
		ProcessData loadFpg = new ProcessData(ProcessData.Type.LANGUAGE, "load_fpg");
		loadFpg.generator = new LoadFpg();
		this.process.put(loadFpg.name, loadFpg);
		
		// LOAD_FNT
		
		ProcessData loadFnt = new ProcessData(ProcessData.Type.LANGUAGE, "load_fnt");
		loadFnt.generator = new LoadFnt();
		this.process.put(loadFnt.name, loadFnt);

		// SET_MODE
		
		ProcessData setMode = new ProcessData(ProcessData.Type.LANGUAGE, "set_mode");
		setMode.generator = new SetMode();
		this.process.put(setMode.name, setMode);

		// SET_FPS
		
		ProcessData setFps = new ProcessData(ProcessData.Type.LANGUAGE, "set_fps");
		setFps.generator = new SetFps();
		this.process.put(setFps.name, setFps);

		// SIGNAL
		
		ProcessData signal = new ProcessData(ProcessData.Type.LANGUAGE, "signal");
		signal.generator = new Signal();
		this.process.put(signal.name, signal);

		// SIGNAL - S_KILL
		
		ProcessData s_kill = new ProcessData(ProcessData.Type.LANGUAGE, "s_kill");
		s_kill.generator = new S_KILL();
		this.process.put(s_kill.name, s_kill);
		
		// WRITE
		
		ProcessData write = new ProcessData(ProcessData.Type.LANGUAGE, "write");
		write.generator = new Write();
		this.process.put(write.name, write);

		// WRITE_var
		
		ProcessData writeVar = new ProcessData(ProcessData.Type.LANGUAGE, "write_var");
		writeVar.generator = new Write();
		this.process.put(writeVar.name, writeVar);

		// DELETE_TEXT
		
		ProcessData deleteText = new ProcessData(ProcessData.Type.LANGUAGE, "delete_text");
		deleteText.generator = new DeleteText();
		this.process.put(deleteText.name, deleteText);

		// COLLISION
		
		ProcessData collision = new ProcessData(ProcessData.Type.LANGUAGE, "collision");
		collision.generator = new Collision();
		this.process.put(collision.name, collision);

		// EXIT
		
		ProcessData exit = new ProcessData(ProcessData.Type.LANGUAGE, "exit");
		exit.generator = new Exit();
		this.process.put(exit.name, exit);

		// ADVANCE
		
		ProcessData advance = new ProcessData(ProcessData.Type.LANGUAGE, "advance");
		advance.generator = new Advance();
		this.process.put(advance.name, advance);

		// AUDIO - ROUTINES
		
		// LOAD_WAV
		
		ProcessData loadWav = new ProcessData(ProcessData.Type.LANGUAGE, "load_wav");
		loadWav.generator = new LoadWav();
		this.process.put(loadWav.name, loadWav);

		// LOAD_SONG
		
		ProcessData loadSong = new ProcessData(ProcessData.Type.LANGUAGE, "load_song");
		loadSong.generator = new LoadSong();
		this.process.put(loadSong.name, loadSong);
		
		// PLAY_WAV
		
		ProcessData playWav = new ProcessData(ProcessData.Type.LANGUAGE, "play_wav");
		playWav.generator = new PlayWav();
		this.process.put(playWav.name, playWav);

		// PLAY_SONG
		
		ProcessData playSong = new ProcessData(ProcessData.Type.LANGUAGE, "play_song");
		playSong.generator = new PlaySong();
		this.process.put(playSong.name, playSong);

		// PAUSE_SONG
		
		ProcessData pauseSong = new ProcessData(ProcessData.Type.LANGUAGE, "pause_song");
		pauseSong.generator = new PauseSong();
		this.process.put(pauseSong.name, pauseSong);

		// RESUME_SONG
		
		ProcessData resumeSong = new ProcessData(ProcessData.Type.LANGUAGE, "resume_song");
		resumeSong.generator = new ResumeSong();
		this.process.put(resumeSong.name, resumeSong);

		// MATH - ROUTINES

		// FGET_DIST
		
		ProcessData fgetDist = new ProcessData(ProcessData.Type.LANGUAGE, "fget_dist");
		fgetDist.generator = new FGetDist();
		this.process.put(fgetDist.name, fgetDist);

		// FGET_ANGLE
		
		ProcessData fgetAngle = new ProcessData(ProcessData.Type.LANGUAGE, "fget_angle");
		fgetAngle.generator = new FGetAngle();
		this.process.put(fgetAngle.name, fgetAngle);
		
		// GRPROC - ROUTINES
		
		// GET_ANGLE
		
		ProcessData getAngle = new ProcessData(ProcessData.Type.LANGUAGE, "get_angle");
		getAngle.generator = new GetAngle();
		this.process.put(getAngle.name, getAngle);

		// GET_DIST
		
		ProcessData getDist = new ProcessData(ProcessData.Type.LANGUAGE, "get_dist");
		getDist.generator = new GetDist();
		this.process.put(getDist.name, getDist);

		// MAP - ROUTINES
		
		// G_WIDTH
		
		ProcessData gWidth = new ProcessData(ProcessData.Type.LANGUAGE, "g_width");
		gWidth.generator = new G_WIDTH();
		this.process.put(gWidth.name, gWidth);

		// G_HEIGHT
		
		ProcessData gHeight = new ProcessData(ProcessData.Type.LANGUAGE, "g_height");
		gHeight.generator = new G_HEIGHT();
		this.process.put(gHeight.name, gHeight);

		// MAP_INFO
		
		ProcessData mapInfo = new ProcessData(ProcessData.Type.LANGUAGE, "map_info");
		mapInfo.generator = new MapInfo();
		this.process.put(mapInfo.name, mapInfo);
		
		// VARIABLES

		ProcessData mouse = new ProcessData(ProcessData.Type.LANGUAGE, "mouse");
		mouse.generator = new Mouse();
		this.process.put(mouse.name, mouse);
		

	}
	
	public void processCode() {
	
		String line = "";
		
		System.out.println("Processing code...");
		for (currentLexema = 0; currentLexema < this.listLexema.size(); currentLexema++) {
			
			Lexema lexema = this.listLexema.get(currentLexema);
			
			if (lexema.tokenType == Token.ID || lexema.group == TokenGroup.LANGUAGE_COMPONENT) {
				line = line + this.identifier(lexema);
			} else {
				line = line + lexema.lexema;
			}
			
		}
		
	}
	
	public String processCodeUntil(Lexema lexema, Token untilToken) {
		
		String line = "";
	
		lexema = MainTools.tools.nextLexema();
		while (lexema.tokenType != untilToken) {
			
			if (lexema.tokenType == Token.ID || lexema.group == TokenGroup.LANGUAGE_COMPONENT) {
				line = line + MainTools.tools.identifier(lexema);
			} else {
				line = line + lexema.lexema;
			}
			
			lexema = MainTools.tools.nextLexema();
		}
		
		return line;
	}

	public String processCodeUntil(Lexema lexema, Token[] untilToken) {
		
		String line = "";
		boolean match = false;
	
		lexema = MainTools.tools.nextLexema();
		for (int i = 0; i < untilToken.length && !match; i++)
			if (lexema.tokenType == untilToken[i])
				match=true;
		while (!match) {
			
			if (lexema.tokenType == Token.ID || lexema.group == TokenGroup.LANGUAGE_COMPONENT) {
				line = line + MainTools.tools.identifier(lexema);
			} else {
				line = line + lexema.lexema;
			}
			
			lexema = MainTools.tools.nextLexema();
			for (int i = 0; i < untilToken.length && !match; i++)
				if (lexema.tokenType == untilToken[i])
					match=true;
				
		}
		
		return line;
	}

	public void normalizeCode() {
		String patter = "((if|while|for|until)\\(.*(\\(.*\\(ProcessJDiv\\)collision\\(.*\\)\\)).*\\))|(if\\(.*(\\(ProcessJDiv\\)collision\\(.*\\)).*\\))|(=[\\+|\\-]*[0-9]+(\\.[0-9]+)*)";
		Pattern pat = Pattern.compile(patter);
		
		Set<String> processSet = this.process.keySet();
		Iterator<String> it = processSet.iterator();
		
		System.out.println("Post-processing...");
		while (it.hasNext()) {
			String name = it.next();
//			ProcessData pData = this.process.get(name);
			ProcessData pData = this.getProcess(name);
			String code = "";
			int from = 0;
			if (pData != null) {
				Matcher m = pat.matcher(pData.code);
				
				while (m.find()) {
				
					if (m.group(3) != null) {
						
						String notNull = m.group(3) + "!= null";
						String expresion = m.group(1);
						expresion = expresion.replace(m.group(3), notNull);
						code = code + pData.code.substring(from, m.start()) + expresion;
						from = m.end();
						
					} else if (m.group(5) != null) {

						String notNull = m.group(5) + "!= null";
						String expresion = m.group(4);
						expresion = expresion.replace(m.group(5), notNull);
						code = code + pData.code.substring(from, m.start()) + expresion;
						from = m.end();

					} else if (m.group(6) != null && m.group(7) != null) {
						
						code = code + pData.code.substring(from, m.start()) + m.group(6) + "f";
						from = m.end();
						
					}
					
//					for (int i = 0; i <= m.groupCount(); i++)
//						if (m.group(i) != null)
//							System.out.println("Grupo " + i + ": " + m.group(i));
				}
				
				code = code + pData.code.substring(from);
				pData.code = code;
			}
		} 
		
	}
	
	public ProcessData getProcess(String name) {
		currentProcess = this.process.get(name);
		return currentProcess;
	}
	
	public void generateProgram(String destination) {
	
		Iterator<String> it = this.process.keySet().iterator();
		
		this.generateLauncher(destination);
		this.generateGlobalVars(destination);
		while (it.hasNext()) {
		
			ProcessData processData = this.process.get(it.next());
		
			if (processData.type == ProcessData.Type.PROCESS || processData.type == ProcessData.Type.FUNCTION)
				generateCode(processData, destination);
			
		}
		
	}
	
	private void generateCode(ProcessData processData, String destination) {
		
		String className = this.formatClassName(processData.name);
		String javaFile = destination + "/" + className + ".java";
		File file = new File(javaFile);
		FileWriter fw;
		System.out.println("Generating " + javaFile + "...");
		try {
			fw = new FileWriter(file);
			BufferedWriter bw = new BufferedWriter(fw);

			String line = "import com.jdiv.JDiv;\nimport com.jdiv.ProcessJDiv;\n";
			
			// Generate the imports
			for (int i = 0; i < processData.imports.size();i++)
				line = line + processData.imports.get(i) + "\n";

			if (processData.type == ProcessData.Type.PROCESS) {
				
				// Generate the class head
				line = line + "public class " + className + " extends ProcessJDiv {\n";
				
				String constructorParams = "ProcessJDiv father";
				String constructorCode = "this.father = father;\n";
				String variables = "";
				String type = "";
				
				// Collect info for the constructor's parameters
				for (int i = 0; i < processData.listVariables.size(); i++) {
					Variable var = processData.listVariables.get(i);
					var.inicializacion = this.normalizeValues(var.inicializacion);
					type = this.getTypeCode(var.type);
					if (var.scope == Variable.Scope.PARAMETER) {
						
						constructorParams = constructorParams + ", " + type + " " + var.name;
						
						constructorCode = constructorCode + "this." + var.name + "=" + var.name + ";\n";

					} 
				
					// Generate the variable's declarations
					if (!this.isReservedVariable(var.name)) {
						if (var.scope == Variable.Scope.PARAMETER || var.scope == Variable.Scope.PRIVATE)
							variables = variables + "private ";
						else
							variables = variables + "public ";
						if (var.elements > 0) {
							variables = variables + type + " " + var.name + "[] " + var.inicializacion + ";\n";
							constructorCode = constructorCode + "this." + var.name + "= new " + type + "[" + var.elements + "];";
						} else
							variables = variables + type + " " + var.name + " " + var.inicializacion + ";\n";
					}

					
				}
				
				line = line + variables;
				line = line + "public " + className + "(" + constructorParams + "){\n";
				line = line + constructorCode + "}\n";
				line = line + processData.code + "}\n";
				
			} else if (processData.type == ProcessData.Type.FUNCTION) {
				
				// Generate the class head
				line = line + "public class " + className + " {\n";
				
//				String constructorParams = "ProcessJDiv father";
				String constructorParams = "";
				//String constructorCode = "this.father = father;\n";
				String variables = "";
				String type = "";
				
				// Collect info for the constructor's parameters
				for (int i = 0; i < processData.listVariables.size(); i++) {
					Variable var = processData.listVariables.get(i);
					type = this.getTypeCode(var.type);
					if (var.scope == Variable.Scope.PARAMETER) {
						
						constructorParams = constructorParams + ", " + type + " " + var.name;
						
					} 
				
					// Generate the variable's declarations
					if (!this.isReservedVariable(var.name) && var.scope != Variable.Scope.PARAMETER) {
						if (var.scope == Variable.Scope.PRIVATE)
							variables = variables + "private static ";
						else
							variables = variables + "public static ";
						
						if (var.elements > 0) {
							
							if (var.inicializacion.isEmpty()) 
								variables = variables + type + " " + var.name + "[] = new " + type + "[" + var.elements + "];";
							else
								variables = variables + type + " " + var.name + "[] " + var.inicializacion + ";\n";
							
						} else
							variables = variables + type + " " + var.name + " " + var.inicializacion + ";\n";
						
					}

				}
				
				if (!constructorParams.isEmpty())
					constructorParams = constructorParams.substring(1);
				line = line + variables;
				line = line + "public static " + processData.returnType + " exec (" + constructorParams + "){\n";
				line = line + processData.code + "}\n}\n";
				
			}

			
			bw.write(line);
			
			bw.close();
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	// Generate Launcher.Java
	private void generateLauncher(String destination) {

		String javaFile = destination + "/Launcher.java";
		
		File file = new File(javaFile);
		
		System.out.println("Generating " + javaFile + "...");
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter(file));
			
			bw.write("import com.badlogic.gdx.backends.lwjgl.LwjglApplication;\n");
			bw.write("import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;\n");
			bw.write("import com.jdiv.JDivRun;\n");
			bw.write("\n");
			bw.write("\n");
			bw.write("public class Launcher {\n");
			bw.write("\n");
			bw.write("	/**\n");
			bw.write("	 * @param args\n");
			bw.write("	 */\n");
			bw.write("\n");
			bw.write("		public static void main(String argv[]) \n");
			bw.write("		{\n");
			bw.write("			LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();\n");
			bw.write("			config.width        = " + this.screenWidth + ";\n");
			bw.write("			config.height       = " + this.screenHeight+ ";\n");
			bw.write("			config.resizable    = false;\n");
			bw.write("			config.title        = \"" + this.programName +"\";\n");
			bw.write("			config.vSyncEnabled = true;\n");
			bw.write("			config.useGL20      = false;\n");
			bw.write("			\n");
			bw.write("			new LwjglApplication( new JDivRun( new Main(null) ), config);\n");
			bw.write("		}\n");
			bw.write("\n");
			bw.write("\n");
			bw.write("}\n");
			
			bw.close();

			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	private void generateGlobalVars(String destination) {
		
		
		String javaFile = destination + "/GlobalVars.java";
		
		File file = new File(javaFile);
		System.out.println("Generating " + javaFile + "...");
		
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter(file));
			
			ProcessData pData = this.getProcess("global");

			String line = "";
			
			// Add imports
			for (int i = 0; i < pData.imports.size(); i++)
				line = line + pData.imports.get(i) + "\n";
			
			// Class header
			line = line + "public class GlobalVars {\n";
			
			
			// Variable's definition
			if (pData != null) {
			
				for (int i = 0; i < pData.listVariables.size(); i++) {
					Variable var = pData.listVariables.get(i);
					
					if (var.elements > 0 )
						if (var.inicializacion.isEmpty())
							line = line + "public static " + this.getTypeCode(var.type) + " " + var.name + "[] = new " + this.getTypeCode(var.type)+ "[" + var.elements + "]"  + ";\n";
						else
							line = line + "public static " + this.getTypeCode(var.type) + " " + var.name + "[] " + var.inicializacion + ";\n";
					else
						line = line + "public static " + this.getTypeCode(var.type) + " " + var.name + " " + var.inicializacion + ";\n";
				}
				
			}
			
			pData = this.getProcess("const");
			
			if (pData != null) {
			
				for (int i = 0; i < pData.listVariables.size(); i++) {
					Variable var = pData.listVariables.get(i);
					line = line + "public static final " + this.getTypeCode(var.type) + " " + var.name + " " + var.inicializacion + ";\n";
				}
				
			}
			
			line = line + "}\n";
			bw.write(line);
			
			bw.close();

			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public String getTypeCode(Variable.Type type) {
		
		return this.types.get(type);
		
	}
	
	public boolean isReservedVariable(String name) {
		
		return (this.reservedVariableNames.get(name) != null);
	}
	
	public String normalizeValues(String line) {
		
		String tmpLine = "";
		
		String pattern = "([\\+|\\-]*[0-9]+(\\.[0-9]+)*)";
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(line);
		int from = 0;
		
		while (m.find()) {

			if (m.group(1) != null && m.group(2) != null) {
				tmpLine = tmpLine + line.substring(from, m.start()) + m.group(1) + "f";
				from = m.end();
			}

		}
		
		
		tmpLine = tmpLine + line.substring(from);
		
		return tmpLine;
		
	}
	
}
